package onvif

import (
	"context"
	"errors"
	"fmt"
	"github.com/beevik/etree"
	"github.com/use-go/onvif"
	onvifdevice "github.com/use-go/onvif/device"
	"github.com/use-go/onvif/media"
	sdkDevice "github.com/use-go/onvif/sdk/device"
	sdkMedia "github.com/use-go/onvif/sdk/media"
	wsdiscovery "github.com/use-go/onvif/ws-discovery"
	xsdOnvif "github.com/use-go/onvif/xsd/onvif"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"net/http"
	"net/url"
	"strings"
	"time"
)

type CameraInfo struct {
	Manufacturer string   `json:"Manufacturer"`
	Model        string   `json:"Model"`
	Name         string   `json:"name"`
	Uri          []string `json:"uri"`
	Auth         bool     `json:"auth"`
	Mac          string   `json:"mac"`
}

type CameraInfoSummary struct {
	Name   string     `json:"name"`
	XAddrs []*url.URL `json:"-"`
	Addx   string     `json:"addx"`
	Uuid   string     `json:"uuid"`
}

type CameraConnection struct {
	Addx     string `json:"addx" binding:"required"`
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

func (cc *CameraConnection) MarshalLogObject(enc zapcore.ObjectEncoder) error {
	enc.AddString("addx", cc.Addx)
	enc.AddString("username", cc.Username)
	enc.AddString("password", cc.Password)
	return nil
}

type Onvif struct {
	log *zap.Logger
	loc *time.Location
}

func NewOnvif(loc *time.Location, log *zap.Logger) *Onvif {
	return &Onvif{log: log.Named("onvif"), loc: loc}
}

// DiscoverySummary 扫描局域网内的开启onvif协议的设备
// 返回设备名称和地址
func (o *Onvif) DiscoverySummary(ifcName string) ([]*CameraInfoSummary, error) {
	devices, err := wsdiscovery.SendProbe(
		ifcName,
		nil,
		[]string{"dn:" + onvif.NVT.String()},
		map[string]string{"dn": "http://www.onvif.org/ver10/network/wsdl"},
	)
	if err != nil {
		return nil, err
	}

	nvtDevicesSeen := make(map[string]bool)
	nvtDevices := make([]*CameraInfoSummary, 0)

	for _, j := range devices {
		doc := etree.NewDocument()
		if err := doc.ReadFromString(j); err != nil {
			return nil, err
		}
		for _, Envelope := range doc.SelectElements("Envelope") {
			ntvDev := &CameraInfoSummary{XAddrs: make([]*url.URL, 0)}
			xaddr := Envelope.FindElement("./Body/ProbeMatches/ProbeMatch/XAddrs")
			scopes := Envelope.FindElement("./Body/ProbeMatches/ProbeMatch/Scopes")
			uuidStr := Envelope.FindElement("./Body/ProbeMatches/ProbeMatch/EndpointReference/Address")

			if scopes == nil || xaddr == nil || uuidStr == nil {
				o.log.Debug("no found addx")
				continue
			}
			//解析xaddrs
			xaddrs := strings.Split(xaddr.Text(), " ")
			for i := range xaddrs {
				url, _err := url.Parse(xaddrs[i])
				if _err != nil {
					continue
				}
				ntvDev.XAddrs = append(ntvDev.XAddrs, url)
				if ntvDev.Addx == "" {
					ntvDev.Addx = url.Host
				}
			}
			//解析uid
			uuids := strings.Split(uuidStr.Text(), ":")
			ntvDev.Uuid = uuids[len(uuids)-1]

			//解析name
			Scopeslist := strings.Split(scopes.Text(), " ")
			for _, scope := range Scopeslist {
				if strings.Contains(scope, "onvif://www.onvif.org/hardware/") {
					ntvDev.Name = strings.ReplaceAll(scope, "onvif://www.onvif.org/hardware/", "")
					break
				}
			}

			if !nvtDevicesSeen[ntvDev.Uuid] {
				nvtDevicesSeen[ntvDev.Uuid] = true
				nvtDevices = append(nvtDevices, ntvDev)
			}
		}
	}
	return nvtDevices, nil

}

func (o *Onvif) GetSnapshotUri(ctx context.Context, cc *CameraConnection) (url string, err error) {
	defer func() {
		if err != nil {
			o.log.Error("GetSnapshotUri error", zap.Object("cc", cc), zap.Error(err))
		} else {
			o.log.Debug("GetSnapshotUri success", zap.Object("cc", cc), zap.String("url", url))
		}
	}()
	if cc.Addx == "" {
		err = errors.New("addx not empty")
		return
	}
	var onvifDev *onvif.Device
	onvifDev, err = o.newDevice(cc)
	if err != nil {
		return
	}
	o.log.Debug(fmt.Sprintf("onvif GetSnapshotUri(onvifDev): %+v", onvifDev.GetServices()))
	getProf := media.GetProfiles{}
	// 获取设备相关配置
	ProfilesRsp, err := sdkMedia.Call_GetProfiles(ctx, onvifDev, getProf)
	if err != nil {
		return
	}
	if len(ProfilesRsp.Profiles) == 0 {
		err = errors.New("no snap url return")
		return
	}
	rsp, err := sdkMedia.Call_GetSnapshotUri(ctx, onvifDev, media.GetSnapshotUri{ProfileToken: ProfilesRsp.Profiles[0].Token})
	if err != nil {
		return
	}
	url = string(rsp.MediaUri.Uri)
	return
}

// CheckConnection 检测onvif设备的连通性
// 返回设备的基本信息
func (o *Onvif) CheckConnection(ctx context.Context, cc *CameraConnection) (cameraST *CameraInfo, err error) {
	cameraST = &CameraInfo{}
	device, err := o.newDevice(cc)
	if err != nil {
		return
	}
	d := onvifdevice.GetDeviceInformation{}
	deviceInformationResponse, err := sdkDevice.Call_GetDeviceInformation(ctx, device, d)
	if err != nil {
		return
	}
	if deviceInformationResponse.SerialNumber == "" {
		err = errors.New("auth error")
		return
	}
	// 查询设备的MAC地址
	interfaceST := onvifdevice.GetNetworkInterfaces{}
	rsp, err := sdkDevice.Call_GetNetworkInterfaces(ctx, device, interfaceST)
	if err != nil {
		return
	}
	deviceMac := strings.ToUpper(strings.ReplaceAll(string(rsp.NetworkInterfaces.Info.HwAddress), ":", ""))
	deviceMac = strings.ToUpper(strings.ReplaceAll(deviceMac, "-", "")) //部分摄像头的MAC地址形式为XX-XX-XX-XX
	cameraST.Mac = deviceMac

	getProf := media.GetProfiles{}
	// 获取设备相关配置
	ProfilesRsp, err := sdkMedia.Call_GetProfiles(ctx, device, getProf)
	if err != nil {
		return
	}

	cameraST.Name = deviceInformationResponse.SerialNumber
	cameraST.Manufacturer = deviceInformationResponse.Manufacturer
	cameraST.Model = deviceInformationResponse.Model

	for i := 0; i < len(ProfilesRsp.Profiles); i++ {
		ss := media.GetStreamUri{
			//只拿RTSP流 HTTP OVER HTTP的暂时不处理
			StreamSetup:  xsdOnvif.StreamSetup{Stream: "RTP-Unicast", Transport: xsdOnvif.Transport{Protocol: "RTSP"}},
			ProfileToken: ProfilesRsp.Profiles[i].Token,
		}
		urlErsp, _err := sdkMedia.Call_GetStreamUri(ctx, device, ss)
		if _err != nil {
			o.log.Error("Call_GetStreamUri error", zap.Error(err))
			continue
		}
		cameraST.Uri = append(cameraST.Uri, string(urlErsp.MediaUri.Uri))
		cameraST.Auth = true
	}

	return cameraST, nil
}

func (o *Onvif) newDevice(cc *CameraConnection) (*onvif.Device, error) {
	client := http.Client{
		Timeout: 10 * time.Second,
	}
	return onvif.NewDevice(onvif.DeviceParams{Xaddr: cc.Addx, Username: cc.Username, Password: cc.Password, HttpClient: &client})
}
